const path = require('path')
const md5 = require('md5')
const fs = require('fs')

const { uniConfigLog, helpTip } = require('./log')
const { get, set, getDate, hasFile, getFileData, writeFileData } = require('./common')

const log = uniConfigLog
let fsWaitTime = 0
let isNotWatchChange = false

function getVarNameReg (configVarName) {
    return new RegExp('(' + configVarName + '[^\.\'"=]+(?:\'|").*/)(.+)(\'|")', 'gi')
}

function getPath (path, data) {
    if (typeof path === 'function') {
        return path(data)
    } else if (Array.isArray(path)) {
        let p = ''
        let res = data
        path.forEach((key, currentIndex) => {
            if (res) {
                if (typeof key === 'function') {
                    if (Array.isArray(res)) {
                        res = res.find((val, index) => {
                            const r = key(val, index)
                            if (r === true) {
                                p = p + (p ? `[${index}]` : index)
                                return true
                            }
                        })
                    } else {
                        for (let k in res) {
                            const r = key(res[k], k)
                            if (r === true) {
                                p = p + (p ? `.` : '') + k
                                res = res[k]
                                break
                            }
                        }
                    }
                } else {
                    res = get(res, key)
                    p = p + (p && key.charAt(0) !== '[' ? '.' : '') + key
                    if (!res && currentIndex === path.length - 1) {
                        res = {}
                    }
                }
            }
        })
        if (!res) {
            p = ''
        }
        return p
    } else if (typeof path === 'string') {
        return path
    } else {
        return ''
    }
}

module.exports = {
    async setManifest (config, configData, configFileName, cwd = global.PRESET_PATH) {
        function setFile (configId, { setWhite, configFile, getConfigFile, watchFile, custom, before, backUp = false } = {}) {
            return new Promise(async (resolve) => {
                let setFile = configFile
                let getFile = (typeof getConfigFile === 'function' ? getConfigFile(configData) : getConfigFile) || configFile
                let res = false
                let isChange = false
                let oldData = await getFileData(setFile)
                let commentsIndex = 0
                let newData = ''
                let topStr = ''
                let sourceData
                const commentsList = {}
                const name = setFile

                if (getFile === false) {
                    log(`${name} 已被外部接管 ...`)
                    return
                }

                sourceData = getFile === setFile ? oldData : await getFileData(getFile)

                log(`${name} init ...`)

                if (before) {
                    log(`前置处理已被接管`)
                    sourceData = before(sourceData, configData)
                    log(`前置处理结束\n`)
                }

                if (!custom) {
                    newData = sourceData.replace(/([^{]*)([\s\S]+)/g, function (_, top, json) {
                        topStr = top
                        return json
                    })

                    newData = newData.replace(/\/\/[\s]*保护区[\s\S]+?\/\/[\s]*保护区/g, function ( data) {
                        const k = '_zs_' + (commentsIndex++)
                        commentsList[k] = data
                        return '"' + k + '": "' + k + '",'
                    })

                    newData = newData.replace(/("[^"]+?"[\s]*\:[\s]*[^[{,:]+?)(\/\/|\/\*)/gi, function (_, item, suffix) {
                        return item + ', __placeholder__: "",' + suffix
                    })

                    newData = newData.replace(/(\/\*[\s\S]*?\*\/)|(\/\/[\s]+.*)/gi, function (item) {
                        const k = '_zs_' + (commentsIndex++)
                        commentsList[k] = item
                        return '"' + k + '": "' + k + '",'
                    })

                    newData = eval(`(${newData})`)
                    for (let k in setWhite) {
                        let val = setWhite[k]
                        let setKey
                        let getKey
                        let defaultValue

                        if (typeof val === 'object' && ((val.setPath && val.getPath) || val.custom)) {
                            if (val.custom) {
                                val.custom(newData, configData)
                            } else {
                                setKey = getPath(val.setPath, newData)
                                getKey = getPath(val.getPath, configData)
                                defaultValue = val.default
                            }
                        } else {
                            setKey = k
                            getKey = val
                            if (typeof getKey === 'object') {
                                defaultValue = getKey.default
                                getKey = getKey.path
                            }
                        }
                        if (setKey) {
                            if (defaultValue === undefined) {
                                defaultValue = ''
                            }
                            set(newData, setKey, get(configData, getKey, defaultValue))
                            isChange = true
                        }
                    }

                    newData = JSON.stringify(newData, null, 2)
                    newData = newData.replace(/"(_zs_[\d]+)[^,\r\n]+,?/gi, function (zs, key) {
                        return commentsList[key] || ''
                    })
                    newData = newData.replace(/,[\s]*"__placeholder__[^,]+?,/gi, '')
                } else {
                    newData = custom({ oldData, currentData: sourceData, config: configData })
                    isChange = true
                }

                if (oldData === topStr + newData) {
                    log(`检测到 ${name} 配置相同（${configId || ''} ${configFileName}），跳过更新\n`)
                    resolve(true)
                    return
                }

                if (isChange) {
                    const copyFile = setFile + '.copy'
                    const isCopyHas = await hasFile(copyFile)
                    log(`正在配置 ${name}`)
                    if (!isCopyHas && backUp) {
                        log(`未发现 ${name} 备份文件，正在备份...`)
                        const cpRes = await writeFileData(copyFile, '// 备份于本地时间 ' + getDate() + '\n// 仅会备份一次，如需重新备份，请删除此文件后重新运行项目即可重新备份\n\n' + oldData)
                        log(`${name} 配置文件备份` + (cpRes ? '成功' : '失败'), copyFile)
                    }
                    res = await writeFileData(setFile, topStr + newData)
                    log(`${name} 配置` + (res ? '成功' : '失败'), `${setFile}\n`)
                } else {
                    log(`未检测到可以配置的 ${name} 信息`)
                }
                resolve(res)
            })
        }
        let allRes = true
        async function loop (configId, list, resolve) {
            const cfg = list.shift()
            const res = await setFile(configId, cfg)
            if (!res) {
                allRes = res
            }
            if (list && list.length) {
                loop(configId, list, resolve)
            } else {
                if (allRes) {
                    log('当前使用配置', (configId || ''), configFileName)
                }
                resolve(allRes)
            }
        }
        return new Promise(async (resolve) => {
            loop(config.configId, [...config.extend], resolve)
        })
    },
    async setConfig (config, configData, configFileName,cwd = global.PRESET_PATH) {
        return new Promise(async (resolve) => {
            let res = false
            let isChange = false
            let configFileData = await getFileData(config.configFile)
            const reg = getVarNameReg(config.configVarName)
            log('config init ...')
            configFileData = configFileData.replace(reg, function (_, l, name, r) {
                isChange = true
                return l + configFileName + r
            })
            if (isChange) {
                log('正在配置 config')
                res = await writeFileData(config.configFile, configFileData)
                log('config 配置' + (res ? '成功' : '失败'), config.configFile, '\n')
                if (res) {
                    isNotWatchChange = true
                }
            } else {
                log('未检测到可以配置的 config 信息')
            }
            resolve(res)
        })
    },
    async updateManifest () {
        const config = await this.getCompanyConfig()
        const configFileData = await getFileData(config.configFile)
        const reg = getVarNameReg(config.configVarName)
        let configFile
        let configFileName

        configFileData.replace(reg, function (_, l, name, r) {
            configFile = name
            configFileName = name
        })

        if (!/\.js/.test(configFile)) {
            configFile += '.js'
        }

        const configData = await this.getConfigData(config.configDir + '/' + configFile)
        if (configData) {
            await this.setManifest(config, configData, configFileName)
        }
    },
    async getConfigData (file) {
        return new Promise(async (resolve) => {
            let configData = await getFileData(file)
            if (configData !== false) {
                configData = configData.replace(/[^{]+([\S\s]+})[\s\S]*/gi, '$1')
                configData = eval('(' + configData + ')')
            }
            resolve(configData)
        })
    },
    async getCompanyConfig (configId) {
        return new Promise(async (resolve) => {
            const file = './uniapp.config.js'
            let config = {}
            const isUniConfigHas = await  hasFile(file)
            if (isUniConfigHas) {
                config = await getFileData(file)
                config = require(path.join(global.PRESET_PATH, file))
                config.configId = configId
            }
            resolve(config)
        })
    },
    async setCompanyConfig (config) {
        return new Promise(async (resolve) => {
            let verifyRes = true
            let configData
            const props = ['configDir', 'configFile', 'configVarName', 'mapFile', 'extend']
            props.forEach(key => {
                if (!config[key]) {
                    verifyRes = false
                }
            })
            if (verifyRes) {
                const mapData = require(path.join(global.PRESET_PATH, config.mapFile)) || {}
                const configFileName = mapData[config.configId]
                let configFile = configFileName
                if (!/\.js/.test(configFile)) {
                    configFile += '.js'
                }
                if (configFile) {
                    let doneMsg = 'warning 自动化配置任务未全部成功执行，请手动检查配置是否正确'
                    configData = await this.getConfigData(config.configDir + '/' + configFile)
                    if (configData) {
                        console.log('\n@description：uni-app自动化配置方案')
                        console.log('@author：梭哈\n')
                        log('配置', config.configId, mapData[config.configId], '...\n')
                        const res1 = await this.setConfig(config, configData, configFileName)
                        const res2 = await this.setManifest(config, configData, configFileName)
                        if (res1 && res2) {
                            doneMsg = 'success 自动化配置任务已全部完成'
                        }
                    }
                    log(doneMsg + '\n')
                } else {
                    log('未找到【' + config.configId + '】对应配置文件，请使用 ' + helpTip , 'warn')
                }
            }
            if (configData === false) {
                config = false
            }
            config.configData = configData
            resolve(config)
        })
    },
    watch (file, options = {}) {
        let preveMd5 = null
        const filePath = file
        const isWatchFile = /(\\|\/)[^\\/]+\..+$/gi.test(file)
        log('Watching ' + file)

        fs.watch(filePath, options, (event, filename) => {
            if (filename && event === 'change') {
                clearTimeout(fsWaitTime)
                fsWaitTime = setTimeout(() => {
                    let currentMd5
                    try {
                        currentMd5 = md5(fs.readFileSync(isWatchFile ? filePath : path.join(filePath, filename)))
                    } catch (e) {
                    }
                    if (currentMd5 == preveMd5) {
                        return
                    }
                    preveMd5 = currentMd5
                    this.updateManifest()
                }, 3)
            }
        })
    },
    async viewMap () {
        const config = await this.getCompanyConfig()
        let mapData = {}
        if (config.mapFile) {
            mapData = require(path.join(global.PRESET_PATH, config.mapFile)) || {}
        }
        log('配置映射', mapData)
        log('使用：', 'yarn <scripts> <key> 请将key放在命令最后 如：yarn wx test')
    },
    async viewCurrent () {
        const config = await this.getCompanyConfig()
        const configFileData = await getFileData(config.configFile)
        const reg = getVarNameReg(config.configVarName)
        let configFile
        configFileData.replace(reg, function (_, l, name, r) {
            configFile = name
        })

        let oldData = await getFileData('./src/manifest.json')
        const manifest = oldData.match(/"description": "(.+?)"/)

        log('当前使用配置')
        log({
            config: configFile,
            manifest: manifest ? manifest[1] : ''
        })

        if (!/\.js/.test(configFile)) {
            configFile += '.js'
        }
        const configData = await this.getConfigData(config.configDir + '/' + configFile)

        log('configData =>', configData)
    },
    async viewItem (configId) {
        const config = await this.getCompanyConfig()
        const mapData = require(path.join(global.PRESET_PATH, config.mapFile)) || {}
        let configFile = mapData[configId]

        if (!/\.js/.test(configFile)) {
            configFile += '.js'
        }
        const configData = await this.getConfigData(config.configDir + '/' + configFile)
        log(configId + ' =>', configData)
    }
}
